﻿
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace Obi
{

    /**
     * Helper class that voxelizes a mesh.
     */
    [Serializable]
    public class MeshVoxelizer
    {
        [Flags]
        public enum Voxel
        {
            Empty = 0,
            Inside = 1 << 0,
            Boundary = 1 << 1,
            Outside = 1 << 2,
        }

        public readonly static Vector3Int[] fullNeighborhood =
        {
            // face neighbors: 
            new Vector3Int(-1,0,0),
            new Vector3Int(1,0,0),
            new Vector3Int(0,-1,0),
            new Vector3Int(0,1,0),
            new Vector3Int(0,0,-1),
            new Vector3Int(0,0,1),

            // edge neighbors:
            new Vector3Int(-1,-1,0),
            new Vector3Int(-1,0,-1),
            new Vector3Int(-1,0,1),
            new Vector3Int(-1,1,0),
            new Vector3Int(0,-1,-1),
            new Vector3Int(0,-1,1),
            new Vector3Int(0,1,-1),
            new Vector3Int(0,1,1),
            new Vector3Int(1,-1,0),
            new Vector3Int(1,0,-1),
            new Vector3Int(1,0,1),
            new Vector3Int(1,1,0),

            // vertex neighbors:
            new Vector3Int(-1,-1,-1),
            new Vector3Int(-1,-1,1),
            new Vector3Int(-1,1,-1),
            new Vector3Int(-1,1,1),
            new Vector3Int(1,-1,-1),
            new Vector3Int(1,-1,1),
            new Vector3Int(1,1,-1),
            new Vector3Int(1,1,1)
        };

        public readonly static Vector3Int[] edgefaceNeighborhood =
        {
            new Vector3Int(-1,-1,0),
            new Vector3Int(-1,0,-1),
            new Vector3Int(-1,0,0),
            new Vector3Int(-1,0,1),
            new Vector3Int(-1,1,0),
            new Vector3Int(0,-1,-1),
            new Vector3Int(0,-1,0),
            new Vector3Int(0,-1,1),
            new Vector3Int(0,0,-1),
            new Vector3Int(0,0,1),
            new Vector3Int(0,1,-1),
            new Vector3Int(0,1,0),
            new Vector3Int(0,1,1),
            new Vector3Int(1,-1,0),
            new Vector3Int(1,0,-1),
            new Vector3Int(1,0,0),
            new Vector3Int(1,0,1),
            new Vector3Int(1,1,0)
        };

        public readonly static Vector3Int[] faceNeighborhood =
        {
            new Vector3Int(-1,0,0),
            new Vector3Int(1,0,0),
            new Vector3Int(0,-1,0),
            new Vector3Int(0,1,0),
            new Vector3Int(0,0,-1),
            new Vector3Int(0,0,1)
        };

        public readonly static Vector3Int[] edgeNeighborhood =
        {
            new Vector3Int(-1,-1,0),
            new Vector3Int(-1,0,-1),
            new Vector3Int(-1,0,1),
            new Vector3Int(-1,1,0),
            new Vector3Int(0,-1,-1),
            new Vector3Int(0,-1,1),
            new Vector3Int(0,1,-1),
            new Vector3Int(0,1,1),
            new Vector3Int(1,-1,0),
            new Vector3Int(1,0,-1),
            new Vector3Int(1,0,1),
            new Vector3Int(1,1,0)
        };

        public readonly static Vector3Int[] vertexNeighborhood =
        {
            new Vector3Int(-1,-1,-1),
            new Vector3Int(-1,-1,1),
            new Vector3Int(-1,1,-1),
            new Vector3Int(-1,1,1),
            new Vector3Int(1,-1,-1),
            new Vector3Int(1,-1,1),
            new Vector3Int(1,1,-1),
            new Vector3Int(1,1,1)
        };

        [NonSerialized] public Mesh input;

        [HideInInspector][SerializeField] private Voxel[] voxels;
        public float voxelSize;
        public Vector3Int resolution;

        private List<int>[] triangleIndices; // temporary structure to hold triangles overlapping each voxel.
        private Vector3Int origin;

        public Vector3Int Origin
        {
            get { return origin; }
        }

        public int voxelCount
        {
            get { return resolution.x * resolution.y * resolution.z; }
        }

        public MeshVoxelizer(Mesh input, float voxelSize)
        {
            this.input = input;
            this.voxelSize = voxelSize;
        }

        public Voxel this[int x, int y, int z]
        {
            get { return voxels[GetVoxelIndex(x, y, z)]; }
            set { voxels[GetVoxelIndex(x, y, z)] = value; }
        }

        public float GetDistanceToNeighbor(int i)
        {
            if (i > 17) return ObiUtils.sqrt3 * voxelSize;
            if (i > 5) return ObiUtils.sqrt2 * voxelSize;
            return voxelSize;
        }

        public int GetVoxelIndex(int x, int y, int z)
        {
            return x + resolution.x * (y + resolution.y * z);
        }

        public Vector3 GetVoxelCenter(in Vector3Int coords)
        {
            return new Vector3(Origin.x + coords.x + 0.5f,
                               Origin.y + coords.y + 0.5f,
                               Origin.z + coords.z + 0.5f) * voxelSize;
        }

        private Bounds GetTriangleBounds(in Vector3 v1, in Vector3 v2, in Vector3 v3)
        {
            Bounds b = new Bounds(v1, Vector3.zero);
            b.Encapsulate(v2);
            b.Encapsulate(v3);
            return b;
        }

        public List<int> GetTrianglesOverlappingVoxel(int voxelIndex)
        {
            if (voxelIndex >= 0 && voxelIndex < triangleIndices.Length)
                return triangleIndices[voxelIndex];
            return null;
        }

        public Vector3Int GetPointVoxel(in Vector3 point)
        {
            return new Vector3Int(Mathf.FloorToInt(point.x / voxelSize),
                                  Mathf.FloorToInt(point.y / voxelSize),
                                  Mathf.FloorToInt(point.z / voxelSize));
        }

        public bool VoxelExists(in Vector3Int coords)
        {
            return VoxelExists(coords.x, coords.y, coords.z);
        }

        public bool VoxelExists(int x, int y, int z)
        {
            return x >= 0 && y >= 0 && z >= 0 &&
                   x < resolution.x &&
                   y < resolution.y &&
                   z < resolution.z;
        }

        private void AppendOverlappingVoxels(in Bounds bounds, in Vector3 v1, in Vector3 v2, in Vector3 v3, int triangleIndex)
        {

            Vector3Int min = GetPointVoxel(bounds.min);
            Vector3Int max = GetPointVoxel(bounds.max);

            for (int x = min.x; x <= max.x; ++x)
                for (int y = min.y; y <= max.y; ++y)
                    for (int z = min.z; z <= max.z; ++z)
                    {
                        Bounds voxel = new Bounds(new Vector3(x + 0.5f, y + 0.5f, z + 0.5f) * voxelSize, Vector3.one * voxelSize);

                        if (IsIntersecting(voxel, v1, v2, v3))
                        {
                            int index = GetVoxelIndex(x - origin.x, y - origin.y, z - origin.z);
                            voxels[index] = Voxel.Boundary;

                            if (triangleIndices != null)
                                triangleIndices[index].Add(triangleIndex);
                        }
                    }
        }


        public IEnumerator Voxelize(Matrix4x4 transform, Vector3Int axisMask, bool generateTriangleIndices = false)
        {
            voxelSize = Mathf.Max(0.0001f, voxelSize);

            var xfBounds = input.bounds.Transform(transform);

            // Calculate min and max voxels, adding a 1-voxel margin.
            origin = GetPointVoxel(Vector3.Scale(xfBounds.min, axisMask)) - axisMask; 
            Vector3Int max = GetPointVoxel(Vector3.Scale(xfBounds.max, axisMask)) + axisMask; 

            resolution = new Vector3Int(max.x - origin.x + 1, max.y - origin.y + 1, max.z - origin.z + 1);

            // Allocate voxels array, and initialize them to "inside" the mesh:
            voxels = new Voxel[resolution.x * resolution.y * resolution.z];

            for (int x = 0; x < resolution.x; ++x)
                for (int y = 0; y < resolution.y; ++y)
                    for (int z = 0; z < resolution.z; ++z)
                        this[x, y, z] = Voxel.Inside;

            // Allocate triangle lists:
            if (generateTriangleIndices)
            {
                triangleIndices = new List<int>[voxels.Length];
                for (int i = 0; i < triangleIndices.Length; ++i)
                    triangleIndices[i] = new List<int>(4);
            }
            else
                triangleIndices = null;

            // Get input triangles and vertices:
            int[] triIndices = input.triangles;
            Vector3[] vertices = input.vertices;

            // Generate surface voxels:
            for (int i = 0; i < triIndices.Length; i += 3)
            {
                Vector3 v1 = Vector3.Scale(transform.MultiplyPoint3x4(vertices[triIndices[i]]), axisMask);
                Vector3 v2 = Vector3.Scale(transform.MultiplyPoint3x4(vertices[triIndices[i + 1]]), axisMask);
                Vector3 v3 = Vector3.Scale(transform.MultiplyPoint3x4(vertices[triIndices[i + 2]]), axisMask);

                Bounds triBounds = GetTriangleBounds(v1, v2, v3);

                AppendOverlappingVoxels(triBounds, v1, v2, v3, i/3);

                if (i % 150 == 0)
                    yield return new CoroutineJob.ProgressInfo("Voxelizing mesh...", i / (float)triIndices.Length);
            }

            // Flood fill outside the mesh. This deals with multiple disjoint regions, and non-watertight models.
            var fillCoroutine = FloodFill();
            while (fillCoroutine.MoveNext())
                yield return fillCoroutine.Current;
        }

        // Ensures boundary is only one voxel thick.
        public void BoundaryThinning()
        {
            for (int x = 0; x < resolution.x; ++x)
                for (int y = 0; y < resolution.y; ++y)
                    for (int z = 0; z < resolution.z; ++z)
                        if (this[x, y, z] == Voxel.Boundary)
                            this[x, y, z] = Voxel.Inside;

            for (int x = 0; x < resolution.x; ++x)
                for (int y = 0; y < resolution.y; ++y)
                    for (int z = 0; z < resolution.z; ++z)
                    {
                        int sum = 0;
                        for (int j = 0; j < faceNeighborhood.Length; ++j)
                        {
                            var index = faceNeighborhood[j];
                            if (VoxelExists(index.x + x, index.y + y, index.z + z) && this[index.x + x, index.y + y, index.z + z] != Voxel.Outside)
                            {
                                sum++;
                            }
                        }

                        if (sum % faceNeighborhood.Length != 0 && this[x, y, z] == Voxel.Inside) 
                            this[x, y, z] = Voxel.Boundary;
                    }
        }

        // Ensures boundary voxels are 2D.
        public void MakeBoundary2D()
        {
            for (int x = 0; x < resolution.x; ++x)
                for (int y = 0; y < resolution.y; ++y)
                    for (int z = 0; z < resolution.z; ++z)
                        if (this[x, y, z] == Voxel.Boundary)
                            this[x, y, z] = Voxel.Inside;

            for (int x = 0; x < resolution.x; ++x)
                for (int y = 0; y < resolution.y; ++y)
                    for (int z = 0; z < resolution.z; ++z)
                    {
                        int sum = 0;
                        for (int j = 0; j < faceNeighborhood.Length; ++j)
                        {
                            var index = faceNeighborhood[j];
                            if (VoxelExists(index.x + x, index.y + y, index.z + z) && this[index.x + x, index.y + y, index.z + z] != Voxel.Outside)
                            {
                                sum++;
                            }
                        }

                        if (sum <= 3 && this[x, y, z] == Voxel.Inside)
                            this[x, y, z] = Voxel.Boundary;
                    }
        }

        public void CreateMesh(ref Mesh mesh, int smoothingIterations)
        {
            if (mesh == null)
                mesh = new Mesh();

            mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
            mesh.Clear();
            List<Vector3> vertices = new List<Vector3>();
            List<Vector3> vertices2 = new List<Vector3>();
            List<int> tris = new List<int>();
            vertices.Clear();
            vertices2.Clear();
            tris.Clear();

            int[] vtxIndex = new int[voxelCount];
            for (int i = 0; i < vtxIndex.Length; ++i)
                vtxIndex[i] = -1;

            // create vertices:
            for (int x = 0; x < resolution.x; ++x)
                for (int y = 0; y < resolution.y; ++y)
                    for (int z = 0; z < resolution.z; ++z)
                        if (this[x, y, z] == Voxel.Boundary)
                        {
                            vtxIndex[GetVoxelIndex(x, y, z)] = vertices.Count;
                            var vtx = new Vector3(Origin.x + x + 0.5f, Origin.y + y + 0.5f, Origin.z + z + 0.5f) * voxelSize;
                            vertices.Add(vtx);
                            vertices2.Add(vtx);
                        }

            List<Vector3> inputVertices = vertices;
            List<Vector3> outputVertices = vertices2;
            for (int i = 0; i < smoothingIterations; ++i)
            {
                for (int x = 0; x < resolution.x; ++x)
                    for (int y = 0; y < resolution.y; ++y)
                        for (int z = 0; z < resolution.z; ++z)
                            if (this[x, y, z] == Voxel.Boundary)
                            {
                                Vector3 avg = Vector3.zero;

                                int count = 0;
                                for (int j = 0; j < faceNeighborhood.Length; ++j)
                                {
                                    var index = faceNeighborhood[j];
                                    if (VoxelExists(index.x + x, index.y + y, index.z + z) && this[index.x + x, index.y + y, index.z + z] == Voxel.Boundary)
                                    {
                                        avg += inputVertices[vtxIndex[GetVoxelIndex(index.x + x, index.y + y, index.z + z)]];
                                        count++;
                                    }
                                }

                                if (count > 0)
                                    outputVertices[vtxIndex[GetVoxelIndex(x, y, z)]] = avg / count;
                            }

                var aux = inputVertices;
                inputVertices = outputVertices;
                outputVertices = aux;
            }

            // triangulate
            for (int x = 0; x < resolution.x; ++x)
                for (int y = 0; y < resolution.y; ++y)
                    for (int z = 0; z < resolution.z; ++z)
                        if (this[x, y, z] == Voxel.Boundary)
                        {
                            int x0y0z0 = GetVoxelIndex(x, y, z);

                            int x1y0z0 = VoxelExists(x + 1, y, z) ? GetVoxelIndex(x+1, y, z) : -1;
                            int x1y1z0 = VoxelExists(x + 1, y + 1, z) ? GetVoxelIndex(x+1, y+1, z) : -1;
                            int x0y1z0 = VoxelExists(x, y + 1, z) ? GetVoxelIndex(x, y+1, z) : -1;

                            int x0y0z1 = VoxelExists(x, y, z + 1) ? GetVoxelIndex(x, y , z + 1) : -1;
                            int x0y1z1 = VoxelExists(x, y + 1, z + 1) ? GetVoxelIndex(x, y + 1, z + 1) : -1;
                            int x1y0z1 = VoxelExists(x + 1, y, z + 1) ? GetVoxelIndex(x+1, y, z + 1) : -1;

                            int x1y1z1 = VoxelExists(x + 1, y + 1, z + 1) ? GetVoxelIndex(x + 1, y + 1, z + 1) : -1;

                            // XY plane
                            if (x1y0z0 >= 0 && x1y1z0 >= 0 && x0y1z0 >= 0 &&
                                voxels[x1y0z0] == Voxel.Boundary &&
                                voxels[x1y1z0] == Voxel.Boundary &&
                                voxels[x0y1z0] == Voxel.Boundary)
                            {
                                if (x0y1z1 < 0 || voxels[x0y1z1] == Voxel.Outside ||
                                    x0y0z1 < 0 || voxels[x0y0z1] == Voxel.Outside ||
                                    x1y0z1 < 0 || voxels[x1y0z1] == Voxel.Outside ||
                                    x1y1z1 < 0 || voxels[x1y1z1] == Voxel.Outside)
                                {
                                    tris.Add(vtxIndex[x0y0z0]);
                                    tris.Add(vtxIndex[x1y0z0]);
                                    tris.Add(vtxIndex[x0y1z0]);

                                    tris.Add(vtxIndex[x0y1z0]);
                                    tris.Add(vtxIndex[x1y0z0]);
                                    tris.Add(vtxIndex[x1y1z0]);
                                }
                                else
                                {
                                    tris.Add(vtxIndex[x1y0z0]);
                                    tris.Add(vtxIndex[x0y0z0]);
                                    tris.Add(vtxIndex[x0y1z0]);

                                    tris.Add(vtxIndex[x1y0z0]);
                                    tris.Add(vtxIndex[x0y1z0]);
                                    tris.Add(vtxIndex[x1y1z0]);
                                }
                            }

                            // XZ plane
                            if (x1y0z0 >= 0 && x1y0z1 >= 0 && x0y0z1 >= 0 &&
                                voxels[x1y0z0] == Voxel.Boundary &&
                                voxels[x1y0z1] == Voxel.Boundary &&
                                voxels[x0y0z1] == Voxel.Boundary)
                            {
                                if (x0y1z0 < 0 || voxels[x0y1z0] == Voxel.Outside ||
                                    x0y1z1 < 0 || voxels[x0y1z1] == Voxel.Outside ||
                                    x1y1z0 < 0 || voxels[x1y1z0] == Voxel.Outside ||
                                    x1y1z1 < 0 || voxels[x1y1z1] == Voxel.Outside)
                                {
                                    tris.Add(vtxIndex[x1y0z0]);
                                    tris.Add(vtxIndex[x0y0z0]);
                                    tris.Add(vtxIndex[x0y0z1]);

                                    tris.Add(vtxIndex[x1y0z0]);
                                    tris.Add(vtxIndex[x0y0z1]);
                                    tris.Add(vtxIndex[x1y0z1]);
                                }
                                else
                                {
                                    tris.Add(vtxIndex[x0y0z0]);
                                    tris.Add(vtxIndex[x1y0z0]);
                                    tris.Add(vtxIndex[x0y0z1]);

                                    tris.Add(vtxIndex[x0y0z1]);
                                    tris.Add(vtxIndex[x1y0z0]);
                                    tris.Add(vtxIndex[x1y0z1]);
                                }
                            }

                            // XY plane
                            if (x0y0z1 >= 0 && x0y1z1 >= 0 && x0y1z0 >= 0 &&
                                voxels[x0y0z1] == Voxel.Boundary &&
                                voxels[x0y1z1] == Voxel.Boundary &&
                                voxels[x0y1z0] == Voxel.Boundary)
                            {
                                if (x1y0z0 < 0 || voxels[x1y0z0] == Voxel.Outside ||
                                    x1y0z1 < 0 || voxels[x1y0z1] == Voxel.Outside ||
                                    x1y1z0 < 0 || voxels[x1y1z0] == Voxel.Outside ||
                                    x1y1z1 < 0 || voxels[x1y1z1] == Voxel.Outside)
                                {
                                    tris.Add(vtxIndex[x0y0z1]);
                                    tris.Add(vtxIndex[x0y0z0]);
                                    tris.Add(vtxIndex[x0y1z0]);

                                    tris.Add(vtxIndex[x0y0z1]);
                                    tris.Add(vtxIndex[x0y1z0]);
                                    tris.Add(vtxIndex[x0y1z1]);
                                }
                                else
                                {
                                    tris.Add(vtxIndex[x0y0z0]);
                                    tris.Add(vtxIndex[x0y0z1]);
                                    tris.Add(vtxIndex[x0y1z0]);

                                    tris.Add(vtxIndex[x0y1z0]);
                                    tris.Add(vtxIndex[x0y0z1]);
                                    tris.Add(vtxIndex[x0y1z1]);
                                }
                            }
                        }

            mesh.SetVertices(outputVertices);
            mesh.SetIndices(tris, MeshTopology.Triangles, 0);
            mesh.RecalculateNormals();
        }

        private IEnumerator FloodFill()
        {
            Queue<Vector3Int> queue = new Queue<Vector3Int>();
            queue.Enqueue(new Vector3Int(0, 0, 0));

            this[0, 0, 0] = Voxel.Outside;

            int i = 0;
            while (queue.Count > 0)
            {
                Vector3Int c = queue.Dequeue();
                Vector3Int v;

                if (c.x < resolution.x - 1 && this[c.x + 1, c.y, c.z] == Voxel.Inside)
                {
                    v = new Vector3Int(c.x + 1, c.y, c.z);
                    this[v.x, v.y, v.z] = Voxel.Outside;
                    queue.Enqueue(v);
                }
                if (c.x > 0 && this[c.x - 1, c.y, c.z] == Voxel.Inside)
                {
                    v = new Vector3Int(c.x - 1, c.y, c.z);
                    this[v.x, v.y, v.z] = Voxel.Outside;
                    queue.Enqueue(v);
                }
                if (c.y < resolution.y - 1 && this[c.x, c.y + 1, c.z] == Voxel.Inside)
                {
                    v = new Vector3Int(c.x, c.y + 1, c.z);
                    this[v.x, v.y, v.z] = Voxel.Outside;
                    queue.Enqueue(v);
                }
                if (c.y > 0 && this[c.x, c.y - 1, c.z] == Voxel.Inside )
                {
                    v = new Vector3Int(c.x, c.y - 1, c.z);
                    this[v.x, v.y, v.z] = Voxel.Outside;
                    queue.Enqueue(v);
                }

                if (c.z < resolution.z - 1 && this[c.x, c.y, c.z + 1] == Voxel.Inside)
                {
                    v = new Vector3Int(c.x, c.y, c.z + 1);
                    this[v.x, v.y, v.z] = Voxel.Outside;
                    queue.Enqueue(v);
                }
                if (c.z > 0 && this[c.x, c.y, c.z - 1] == Voxel.Inside)
                {
                    v = new Vector3Int(c.x, c.y, c.z - 1);
                    this[v.x, v.y, v.z] = Voxel.Outside;
                    queue.Enqueue(v);
                }

                if (++i % 150 == 0)
                    yield return new CoroutineJob.ProgressInfo("Filling mesh...", i / (float)voxels.Length);
            }
        }

        public static bool IsIntersecting(in Bounds box, Vector3 v1, Vector3 v2, Vector3 v3)
        {
            v1 -= box.center;
            v2 -= box.center;
            v3 -= box.center;

            var ab = v2 - v1;
            var bc = v3 - v2;
            var ca = v1 - v3;

            //cross with (1, 0, 0)
            var a00 = new Vector3(0, -ab.z, ab.y);
            var a01 = new Vector3(0, -bc.z, bc.y);
            var a02 = new Vector3(0, -ca.z, ca.y);

            //cross with (0, 1, 0)
            var a10 = new Vector3(ab.z, 0, -ab.x);
            var a11 = new Vector3(bc.z, 0, -bc.x);
            var a12 = new Vector3(ca.z, 0, -ca.x);

            //cross with (0, 0, 1)
            var a20 = new Vector3(-ab.y, ab.x, 0);
            var a21 = new Vector3(-bc.y, bc.x, 0);
            var a22 = new Vector3(-ca.y, ca.x, 0);

            if (
                !TriangleAabbSATTest(v1, v2, v3, box.extents, a00) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, a01) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, a02) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, a10) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, a11) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, a12) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, a20) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, a21) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, a22) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, Vector3.right) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, Vector3.up) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, Vector3.forward) ||
                !TriangleAabbSATTest(v1, v2, v3, box.extents, Vector3.Cross(ab, bc))
            )
                return false;

            return true;
        }

        static bool TriangleAabbSATTest(in Vector3 v0, in Vector3 v1, in Vector3 v2, in Vector3 aabbExtents, in Vector3 axis)
        {
            float p0 = Vector3.Dot(v0, axis);
            float p1 = Vector3.Dot(v1, axis);
            float p2 = Vector3.Dot(v2, axis);

            float r = aabbExtents.x * Mathf.Abs(axis.x) +
                      aabbExtents.y * Mathf.Abs(axis.y) +
                      aabbExtents.z * Mathf.Abs(axis.z);

            float maxP = Mathf.Max(p0, Mathf.Max(p1, p2));
            float minP = Mathf.Min(p0, Mathf.Min(p1, p2));

            return !(Mathf.Max(-maxP, minP) > r);
        }
    }
}
